﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Linq.Expressions;
using System.Windows.Forms;
using CSW.Framework.Common;
using CSW.Framework.Common.Binding;
using CSW.Framework.Common.Binding.Interfaces;
using CSW.Framework.Linq;

namespace CSW.Framework.UI.Binding
{
    /// <summary>
    /// Provides capability to bind controls and error providers to properties in an <see cref="IBindableObject" />.
    /// </summary>
    public class SearchBinder<T> : Binder
        where T : class, IBusinessObjectLinq, new()
    {
        private readonly BindableSearchObject m_SearchObject;
        private readonly List<Where<T>> m_ExplicitWhere = new List<Where<T>>();
        private readonly ITableQueryProvider<T> m_TableQueryProvider;
        private Control m_SearchButton;
        private ITypedGridControllerLinq<T> m_TypedGridController;
        private bool m_VerboseSearch;

        private interface ISearchProperty
        {
            Where<T> GetWhere();
        }

        private class BindableSearchObject : BindableObject
        {
            public class SearchProperty<TValue> : Property<TValue>, ISearchProperty
            {
                public ComparisonType ComparisonType { get; private set; }
                public Expression<Func<T, TValue>> TargetProperty { get; private set; }

                public SearchProperty(BindableObject bindableObject, string propertyName, Expression<Func<T, TValue>> property, ComparisonType comparisonType)
                    : base(bindableObject, propertyName)
                {
                    Guard.ArgumentNotNull(property, "property");

                    TargetProperty = property;
                    ComparisonType = comparisonType;
                }

                public Where<T> GetWhere()
                {
                    if ((typeof(TValue) == typeof(DateTime?) || typeof(TValue) == typeof(DateTime)) && ComparisonType == ComparisonType.LessThanOrEqual)
                    {
                        DateTime? value = (DateTime?)(object)Value;
                        if (value != null)
                            value = value.Value.AddDays(1).Date;

                        return Where<T>.Create(TargetProperty, ComparisonType.LessThan, value);
                    }
                    else
                    {
                        return Where<T>.Create(TargetProperty, ComparisonType, Value);
                    }
                }
            }

            public void AddProperty<TResult>(Expression<Func<T, TResult>> property, string propertyName, ComparisonType comparisonType)
            {
                Guard.ArgumentNotNull(property, "property");
                Guard.ArgumentNotNull(propertyName, "propertyName");

                new SearchProperty<TResult>(this, propertyName, property, comparisonType);
            }
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="SearchBinder&lt;T&gt;"/> class.
        /// </summary>
        public SearchBinder()
            : base(new BindableSearchObject(), new ErrorWrapper(new ErrorProvider()), Binding.ShowErrors.Immediately)
        {
            m_SearchObject = (BindableSearchObject)BindableObject;
            m_TableQueryProvider = new TableQueryProvider<T>();
        }

        /// <summary>
        /// Binds a control to a property in the bindable object.
        /// </summary>
        /// <param name="bindableControl">The bindable control.</param>
        /// <param name="property">Expression of the property.</param>
        public string Bind<TResult>(IBindableControl bindableControl, Expression<Func<T, TResult>> property)
        {
            return Bind(bindableControl, property, ComparisonType.Equals);
        }

        /// <summary>
        /// Binds a control to a property in the bindable object.
        /// </summary>
        /// <typeparam name="TResult">The type of the result.</typeparam>
        /// <param name="bindableControl">The bindable control.</param>
        /// <param name="property">Expression of the property.</param>
        /// <param name="comparisonType">Type of the comparison.</param>
        /// <returns>The property name.</returns>
        public string Bind<TResult>(IBindableControl bindableControl, Expression<Func<T, TResult>> property, ComparisonType comparisonType)
        {
            Guard.ArgumentNotNull(bindableControl, "bindableControl");
            Guard.ArgumentNotNull(property, "property");

            string propertyName = LinqToSqlHelper.GetFieldName(property);

            string searchPropertyName = GetUniquePropertyName(propertyName.Replace(".", "-") + "|" + comparisonType);

            m_SearchObject.AddProperty(property, searchPropertyName, comparisonType);

            base.Bind(bindableControl, searchPropertyName);

            return propertyName;
        }

        private string GetUniquePropertyName(string propertyName)
        {
            Guard.ArgumentNotNullOrEmptyString(propertyName, "propertyName");

            string newPropertyName = propertyName;

            List<string> list = m_SearchObject.Properties.Select(p => p.PropertyName).ToList();
            int i = 2;
            while (list.Contains(newPropertyName))
            {
                newPropertyName = propertyName + "_" + i++;
            }

            return newPropertyName;
        }

        /// <summary>
        /// Binds a control to a property in the bindable object.
        /// </summary>
        /// <param name="bindableControl">The bindable control.</param>
        /// <param name="propertyName">Name of the property.</param>
        public override void Bind(IBindableControl bindableControl, string propertyName)
        {
            throw new NotSupportedException(string.Format("Use 'public void Bind<TResult>(IBindableControl bindableControl, Expression<Func<T, TResult>> property)' when working with SearchBinder<T>."));
        }

        /// <summary>
        /// Gets the list of where clauses.
        /// </summary>
        /// <returns>The list of where clauses.</returns>
        public List<Where<T>> GetWhere()
        {
            List<Where<T>> whereList = new List<Where<T>>();
            foreach (ISearchProperty searchProperty in m_SearchObject.Properties.OfType<ISearchProperty>())
            {
                whereList.Add(searchProperty.GetWhere());
            }
            whereList.AddRange(m_ExplicitWhere);
            return whereList;
        }

        /// <summary>
        /// Adds an explicit where clause.
        /// </summary>
        /// <typeparam name="TResult">The type of the result.</typeparam>
        /// <param name="property">The property.</param>
        /// <param name="comparisonType">Type of the comparison.</param>
        /// <param name="value">The value.</param>
        public void AddExplicitWhere<TResult>(Expression<Func<T, TResult>> property, ComparisonType comparisonType, object value)
        {
            m_ExplicitWhere.Add(Where<T>.Create(property, comparisonType, value));
        }

        /// <summary>
        /// Binds the search controller to a grid.
        /// </summary>
        /// <param name="searchButton">The search button.</param>
        /// <param name="typedGridController">The typed grid controller.</param>
        /// <param name="verbose">if set to <c>true</c>, a message box will be displayed if there are no records.</param>
        public void BindToGrid(Control searchButton, ITypedGridControllerLinq<T> typedGridController, bool verbose)
        {
            BindToGrid(searchButton, null, typedGridController, verbose);
        }

        /// <summary>
        /// Binds the search controller to a grid.
        /// </summary>
        /// <param name="searchButton">The search button.</param>
        /// <param name="clearButton">The clear button.</param>
        /// <param name="typedGridController">The typed grid controller.</param>
        /// <param name="verbose">if set to <c>true</c>, a message box will be displayed if there are no records.</param>
        public void BindToGrid(Control searchButton, Control clearButton, ITypedGridControllerLinq<T> typedGridController, bool verbose)
        {
            //Guard.ArgumentNotNull(searchButton, "searchButton");
            Guard.ArgumentNotNull(typedGridController, "typedGridController");

            if (m_SearchButton != searchButton)
            {
                if (m_SearchButton != null)
                    m_SearchButton.Click -= m_SearchButton_Click;

                m_SearchButton = searchButton;
                if (m_SearchButton != null)
                    m_SearchButton.Click += m_SearchButton_Click;
            }

            if (clearButton != null)
            {
                clearButton.Click += delegate { Clear(); };
            }

            m_TypedGridController = typedGridController;

            m_VerboseSearch = verbose;

            m_TypedGridController.CreateTypeAsync(GetWhere());
            m_TypedGridController.BestFitColumns();
        }

        private void m_SearchButton_Click(object sender, EventArgs e)
        {
            DoSearch();
        }

        /// <summary>
        /// Clears the search values.
        /// </summary>
        public void Clear()
        {
            Cursor current = Cursor.Current;
            Cursor.Current = Cursors.WaitCursor;
            string firstProperty = string.Empty;

            foreach (ComplexProperty property in m_SearchObject.Properties)
            {
                if (string.IsNullOrEmpty(firstProperty))
                    firstProperty = property.PropertyName;
                property.ClearValue();
            }
            FocusControl(firstProperty);
            Cursor.Current = current;
        }

        /// <summary>
        /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
        /// </summary>
        public override void Dispose()
        {
            if (m_SearchButton != null)
                m_SearchButton.Click -= m_SearchButton_Click;

            base.Dispose();
        }

        /// <summary>
        /// Does the search.
        /// </summary>
        public void DoSearch()
        {
            DoSearch(m_VerboseSearch);
        }

        /// <summary>
        /// Does the search.
        /// </summary>
        /// <param name="verbose">if set to <c>true</c>, a message box will be displayed if there are no records.</param>
        public void DoSearch(bool verbose)
        {
            if (m_TypedGridController == null)
                throw new Exception(string.Format("Call BindToGrid to set the grid before performing search with SearchBinder<{0}>", typeof(T).FullName));

            Cursor currentCursor = Cursor.Current;
            Cursor.Current = Cursors.WaitCursor;

            IBindingList bindingList = m_TypedGridController.Query(m_TableQueryProvider, GetWhere());
            m_TypedGridController.BestFitColumns();
            Cursor.Current = currentCursor;
            if (verbose && (bindingList == null || bindingList.Count == 0))
                MessageBox.Show("No records found.", "", MessageBoxButtons.OK, MessageBoxIcon.Information);
        }
    }
}
